﻿using System.Reflection;
using IdentityServer4.EntityFramework.Storage;
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Reborn.IdentityServer4.Admin.EntityFramework.Configuration.Configuration;
using Reborn.IdentityServer4.Admin.EntityFramework.Interfaces;
using Reborn.IdentityServer4.Admin.AuditLogging.EntityFramework.DbContexts;
using Reborn.IdentityServer4.Admin.AuditLogging.EntityFramework.Entities;

namespace Reborn.IdentityServer4.Admin.EntityFramework.Configuration.MySql;

public static class DatabaseExtensions
{
    /// <summary>
    ///     Register DbContexts for IdentityServer ConfigurationStore and PersistedGrants, Identity and Logging
    ///     Configure the connection strings in AppSettings.json
    /// </summary>
    /// <typeparam name="TConfigurationDbContext"></typeparam>
    /// <typeparam name="TPersistedGrantDbContext"></typeparam>
    /// <typeparam name="TLogDbContext"></typeparam>
    /// <typeparam name="TIdentityDbContext"></typeparam>
    /// <typeparam name="TAuditLoggingDbContext"></typeparam>
    /// <typeparam name="TDataProtectionDbContext"></typeparam>
    /// <typeparam name="TAuditLog"></typeparam>
    /// <param name="services"></param>
    /// <param name="connectionStrings"></param>
    /// <param name="databaseMigrations"></param>
    public static void RegisterMySqlDbContexts<TIdentityDbContext, TConfigurationDbContext,
        TPersistedGrantDbContext, TLogDbContext, TAuditLoggingDbContext, TDataProtectionDbContext, TAuditLog>(
        this IServiceCollection services,
        ConnectionStringsConfiguration connectionStrings,
        DatabaseMigrationsConfiguration databaseMigrations)
        where TIdentityDbContext : DbContext
        where TPersistedGrantDbContext : DbContext, IAdminPersistedGrantDbContext
        where TConfigurationDbContext : DbContext, IAdminConfigurationDbContext
        where TLogDbContext : DbContext, IAdminLogDbContext
        where TAuditLoggingDbContext : DbContext, IAuditLoggingDbContext<TAuditLog>
        where TDataProtectionDbContext : DbContext, IDataProtectionKeyContext
        where TAuditLog : AuditLog
    {
        var migrationsAssembly = typeof(DatabaseExtensions).GetTypeInfo().Assembly.GetName().Name;

        // Config DB for identity
        services.AddDbContext<TIdentityDbContext>(options =>
            options.UseMySql(connectionStrings.IdentityDbConnection,
                ServerVersion.AutoDetect(connectionStrings.IdentityDbConnection),
                sql => sql.MigrationsAssembly(databaseMigrations.IdentityDbMigrationsAssembly ?? migrationsAssembly)));

        // Config DB from existing connection
        services.AddConfigurationDbContext<TConfigurationDbContext>(options =>
            options.ConfigureDbContext = b =>
                b.UseMySql(connectionStrings.ConfigurationDbConnection,
                    ServerVersion.AutoDetect(connectionStrings.ConfigurationDbConnection),
                    sql => sql.MigrationsAssembly(databaseMigrations.ConfigurationDbMigrationsAssembly ??
                                                  migrationsAssembly)));

        // Operational DB from existing connection
        services.AddOperationalDbContext<TPersistedGrantDbContext>(options => options.ConfigureDbContext = b =>
            b.UseMySql(connectionStrings.PersistedGrantDbConnection,
                ServerVersion.AutoDetect(connectionStrings.PersistedGrantDbConnection),
                sql => sql.MigrationsAssembly(databaseMigrations.PersistedGrantDbMigrationsAssembly ??
                                              migrationsAssembly)));

        // Log DB from existing connection
        services.AddDbContext<TLogDbContext>(options => options.UseMySql(connectionStrings.AdminLogDbConnection,
            ServerVersion.AutoDetect(connectionStrings.AdminLogDbConnection),
            optionsSql => optionsSql.MigrationsAssembly(databaseMigrations.AdminLogDbMigrationsAssembly ??
                                                        migrationsAssembly)));

        // Audit logging connection
        services.AddDbContext<TAuditLoggingDbContext>(options => options.UseMySql(
            connectionStrings.AdminAuditLogDbConnection,
            ServerVersion.AutoDetect(connectionStrings.AdminAuditLogDbConnection),
            optionsSql => optionsSql.MigrationsAssembly(databaseMigrations.AdminAuditLogDbMigrationsAssembly ??
                                                        migrationsAssembly)));

        // DataProtectionKey DB from existing connection
        if (!string.IsNullOrEmpty(connectionStrings.DataProtectionDbConnection))
            services.AddDbContext<TDataProtectionDbContext>(options => options.UseMySql(
                connectionStrings.DataProtectionDbConnection,
                ServerVersion.AutoDetect(connectionStrings.DataProtectionDbConnection),
                optionsSql => optionsSql.MigrationsAssembly(databaseMigrations.DataProtectionDbMigrationsAssembly ??
                                                            migrationsAssembly)));
    }

    /// <summary>
    ///     Register DbContexts for IdentityServer ConfigurationStore and PersistedGrants and Identity
    ///     Configure the connection strings in AppSettings.json
    /// </summary>
    /// <typeparam name="TConfigurationDbContext"></typeparam>
    /// <typeparam name="TPersistedGrantDbContext"></typeparam>
    /// <typeparam name="TIdentityDbContext"></typeparam>
    /// <typeparam name="TDataProtectionDbContext"></typeparam>
    /// <param name="services"></param>
    /// <param name="identityConnectionString"></param>
    /// <param name="configurationConnectionString"></param>
    /// <param name="persistedGrantConnectionString"></param>
    /// <param name="dataProtectionConnectionString"></param>
    public static void RegisterMySqlDbContexts<TIdentityDbContext, TConfigurationDbContext,
        TPersistedGrantDbContext, TDataProtectionDbContext>(this IServiceCollection services,
        string identityConnectionString, string configurationConnectionString,
        string persistedGrantConnectionString, string dataProtectionConnectionString)
        where TIdentityDbContext : DbContext
        where TPersistedGrantDbContext : DbContext, IAdminPersistedGrantDbContext
        where TConfigurationDbContext : DbContext, IAdminConfigurationDbContext
        where TDataProtectionDbContext : DbContext, IDataProtectionKeyContext
    {
        var migrationsAssembly = typeof(DatabaseExtensions).GetTypeInfo().Assembly.GetName().Name;

        // Config DB for identity
        services.AddDbContext<TIdentityDbContext>(options => options.UseMySql(identityConnectionString,
            ServerVersion.AutoDetect(identityConnectionString), sql => sql.MigrationsAssembly(migrationsAssembly)));

        // Config DB from existing connection
        services.AddConfigurationDbContext<TConfigurationDbContext>(options => options.ConfigureDbContext = b
            => b.UseMySql(configurationConnectionString, ServerVersion.AutoDetect(configurationConnectionString),
                sql => sql.MigrationsAssembly(migrationsAssembly)));

        // Operational DB from existing connection
        services.AddOperationalDbContext<TPersistedGrantDbContext>(options => options.ConfigureDbContext = b
            => b.UseMySql(persistedGrantConnectionString, ServerVersion.AutoDetect(persistedGrantConnectionString),
                sql => sql.MigrationsAssembly(migrationsAssembly)));

        // DataProtectionKey DB from existing connection
        services.AddDbContext<TDataProtectionDbContext>(options => options.UseMySql(dataProtectionConnectionString,
            ServerVersion.AutoDetect(dataProtectionConnectionString),
            optionsSql => optionsSql.MigrationsAssembly(migrationsAssembly)));
    }
}